
/**
 * LICENCIA LGPL:
 * 
 * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
 * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
 * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
 * la Licencia, o (a su elección) cualquier versión posterior.
 * 
 * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
 * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
 * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
 * License (LGPL) para más detalles
 * 
 * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
 * junto con esta librería; si no es así, escriba a la Free Software Foundation 
 * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
 * <http://www.gnu.org/licenses/>.
 *
 * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
 */
package es.accv.arangi.base.signature;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;

import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import es.accv.arangi.base.certificate.Certificate;
import es.accv.arangi.base.certificate.validation.CAList;
import es.accv.arangi.base.device.DeviceManager;
import es.accv.arangi.base.document.FileDocument;
import es.accv.arangi.base.document.IDocument;
import es.accv.arangi.base.document.URLDocument;
import es.accv.arangi.base.exception.TimeStampException;
import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
import es.accv.arangi.base.exception.device.LoadingObjectException;
import es.accv.arangi.base.exception.device.SearchingException;
import es.accv.arangi.base.exception.document.HashingException;
import es.accv.arangi.base.exception.document.InitDocumentException;
import es.accv.arangi.base.exception.CounterSignatureException;
import es.accv.arangi.base.exception.signature.NoCoincidentDocumentException;
import es.accv.arangi.base.exception.signature.NoDocumentToSignException;
import es.accv.arangi.base.exception.signature.RetrieveOCSPException;
import es.accv.arangi.base.exception.signature.SignatureException;
import es.accv.arangi.base.exception.signature.SignatureNotFoundException;
import es.accv.arangi.base.exception.signature.XMLDocumentException;
import es.accv.arangi.base.exception.timestamp.MalformedTimeStampException;
import es.accv.arangi.base.exception.timestamp.ResponseTimeStampException;
import es.accv.arangi.base.mityc.ArangiCertStatusRecover;
import es.accv.arangi.base.mityc.ContraFirmaXML;
import es.accv.arangi.base.timestamp.TimeStamp;
import es.accv.arangi.base.util.Util;
import es.accv.arangi.base.util.validation.ValidationResult;
import es.mityc.firmaJava.libreria.ConstantesXADES;
import es.mityc.firmaJava.libreria.excepciones.AddXadesException;
import es.mityc.firmaJava.libreria.utilidades.Base64Coder;
import es.mityc.firmaJava.libreria.utilidades.I18n;
import es.mityc.firmaJava.libreria.utilidades.NombreNodo;
import es.mityc.firmaJava.libreria.utilidades.UtilidadFicheros;
import es.mityc.firmaJava.libreria.utilidades.UtilidadFirmaElectronica;
import es.mityc.firmaJava.libreria.utilidades.UtilidadTratarNodo;
import es.mityc.firmaJava.libreria.xades.DataToSign;
import es.mityc.firmaJava.libreria.xades.EnumFormatoFirma;
import es.mityc.firmaJava.libreria.xades.RespYCerts;
import es.mityc.firmaJava.libreria.xades.ResultadoEnum;
import es.mityc.firmaJava.libreria.xades.ResultadoValidacion;
import es.mityc.firmaJava.libreria.xades.UtilidadXadesA;
import es.mityc.firmaJava.libreria.xades.XAdESSchemas;
import es.mityc.firmaJava.libreria.xades.errores.BadFormedSignatureException;
import es.mityc.firmaJava.libreria.xades.errores.FirmaXMLError;
import es.mityc.firmaJava.ts.ConstantesTSA;
import es.mityc.firmaJava.ts.TSCliente;
import es.mityc.firmaJava.ts.TSClienteError;
import es.mityc.javasign.certificate.CertStatusException;

/**
 * Clase que maneja firmas en formato XAdES-X-L de acuerdo al estándar 
 * <a href="http://uri.etsi.org/01903/v1.3.2/ts_101903v010302p.pdf" target="etsi">
 * ETSI TS 101 903</a><br><br>
 * 
 * El formato XAdES-A también es tratado por esta clase. Gracias al método 
 * {@link #getTimeStampCertificateExpiration() getTimeStampCertificateExpiration} es
 * posible determinar la fecha de caducidad del certificado de TSA. Antes de que se
 * llegue a esta fecha es conveniente resellar la firma con un sello de archivo
 * mediante el método {@link #addArchiveTimeStamp(URL) addArchiveTimeStamp}.<br><br>
 *  
 * Ejemplo de uso: <br><br>
 * 
 * <code> 
 * KeyStoreManager manager = new KeyStoreManager (..., ...);<br>
 * String alias = ...;<br>
 * InputStreamDocument documentTexto = new InputStreamDocument (new FileInputStream (...));<br>
 * InputStreamDocument documentXML = new InputStreamDocument (new FileInputStream (...));<br>
 * File file = new File (...);<br>
 * URL url = new URL (...);<br>
 * URL urlTSA = new URL (...);<br>
 * CAList caList = new CAList (...);<br><br>
 * 
 * //-- Genera una firma attached. El documento se guardará en la firma en base64<br>
 * XAdESXLSignature signature1 = XAdESXLSignature.signAttached(manager, alias, documentTexto, urlTSA, caList);<br><br>
 * 
 * //-- Genera una firma detached que referencia al fichero en disco<br>
 * XAdESXLSignature signature2 = XAdESXLSignature.signDetached(manager, alias, file, urlTSA, caList);<br><br>
 * 
 * //-- Genera una firma detached que referencia a "2011/04/29/certificados/CER-2584665.pdf"<br>
 * XAdESXLSignature signature3 = XAdESXLSignature.signDetached(manager, alias, file, "2011/04/29/certificados/CER-2584665.pdf", urlTSA, caList);<br><br>
 * 
 * //-- Genera una firma detached que referencia al fichero ubicado en la URL<br>
 * XAdESXLSignature signature4 = XAdESXLSignature.signDetached(manager, alias, url, urlTSA, caList);<br><br>
 * 
 * //-- Genera una firma attached dentro del propio documento<br>
 * XAdESXLSignature signature5 = XAdESXLSignature.signAttached(manager, alias, documentoXML, "titulo", "documento", urlTSA, caList);<br><br>
 * </code>
 * 
 * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
 */
public class XAdESXLSignature extends XAdESSignature {
	
	/*
	 * Logger de la clase
	 */
	static Logger logger = Logger.getLogger(XAdESXLSignature.class);
	
	/*
	 * Referencia por defecto al node SignatureTimeStamp
	 */
	protected static final String DEFAULT_SIGNATURE_TIMESTAMP_ID 	= "SignTimeStamp";
	
	/*
	 * URI para espacios de nombres XADES
	 */
	protected static final String NAMESPACE_XADES_URI = "http://uri.etsi.org/01903/v1.3.2#";

	/*
	 * Constante que indica el algoritmo de canonicalización por defecto
	 */
	protected static final String DEFAULT_CANONICALIZATION_ALGORITHM	= CanonicalizationMethod.INCLUSIVE;
	

	//-- Constructores
	
	/**
	 * Construye el objeto en base a un XML que tiene el formato
	 * XAdES-X-L
	 * 
	 * @param xmlDocument Documento XML
	 */
	public XAdESXLSignature(Document xmlDocument) {
		initialize (xmlDocument);
	}
	
	/**
	 * Construye el objeto en base a un fichero XAdES-X-L
	 * 
	 * @param xmlFile Fichero XAdES-X-L
	 * @throws FileNotFoundException El fichero no existe
	 * @throws XMLDocumentException El fichero no parece un XML válido
	 */
	public XAdESXLSignature(File xmlFile) throws FileNotFoundException, XMLDocumentException {
		initialize (xmlFile);
	}

	/**
	 * Construye el objeto en base a un array de bytes.
	 * 
	 * @param signature Firma XAdES-X-L
	 * @throws XMLDocumentException El fichero no parece un XML válido
	 */
	public XAdESXLSignature(byte[] signature) throws XMLDocumentException {
		initialize (signature);
	}

	/**
	 * Construye el objeto en base a un stream de lectura.
	 * 
	 * @param isSignature Stream de lectura a una firma XAdES-X-L
	 * @throws XMLDocumentException El fichero no parece un XML válido
	 */
	public XAdESXLSignature(InputStream isSignature) throws XMLDocumentException {
		initialize(isSignature);
	}

	/**
	 * Construye el objeto en base a otro XAdES-X-L
	 * 
	 * @param xlSignature Firma XAdES-X-L
	 */
	protected XAdESXLSignature(XAdESXLSignature xlSignature) {
		xadesDocument = xlSignature.xadesDocument;
	}
	
	//-- Métodos públicos
	
	/**
	 * Obtiene la fecha del sello de tiempos de la firma (Punto 7.3 del estándar).
	 * 
	 * @return Fecha del sello de tiempos
	 * @throws MalformedTimeStampException El sello de tiempos guardado en el fichero XAdES-X-L no 
	 * está bien formado
	 */
	public Date getTimeStampTime () throws MalformedTimeStampException {
		return getXAdESTimeStampTime();

	}
	
	/**
	 * Realiza una firma XAdES-X-L detached (el fichero no se incluirá en la firma). No completa los campos 
	 * no obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 
	 * 'signatureProductionPlace' y 'signerRole'.
	 * 
	 * La referencia a la que apuntará la firma será el path del fichero.
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Fichero a firmar
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 * @throws NoDocumentToSignException El fichero a firmar no existe o es nulo
	 */
	public static XAdESXLSignature signDetached (DeviceManager manager, String alias, File document, URL urlTSA, CAList caList) throws LoadingObjectException, SignatureException, NoDocumentToSignException {
		
		logger.debug("[XAdESXLSignature.signDetached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, urlTSA, caList }));
		
		//-- Obtener el document de Arangi y llamar a la firma attached
		FileDocument fileDocument;
		try {
			fileDocument = new FileDocument(document);
		} catch (InitDocumentException e) {
			logger.info("[XAdESXLSignature.signDetached]::El fichero a firmar no existe o es nulo:: " + document);
			throw new NoDocumentToSignException("El fichero a firmar no existe o es nulo: " + document);
		}
		
		//-- Obtener la referencia al fichero
		String reference = UtilidadFicheros.relativizeRute("#", document);
		
		return signDetached(manager, alias, fileDocument, reference, urlTSA, caList);
		
	}
	
	/**
	 * Realiza una firma XAdES-XL detached (el fichero no se incluirá en la firma). No completa los campos 
	 * no obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 
	 * 'signatureProductionPlace' y 'signerRole'.
	 * 
	 * La referencia a la que apuntará la firma será la URL del documento.
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param urlDocument Documento a firmar. Se encuentra en una URL accesible.
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 * @throws NoDocumentToSignException La URL a firmar no existe o es nula
	 */
	public static XAdESXLSignature signDetached (DeviceManager manager, String alias, URL urlDocument, URL urlTSA, CAList caList) throws LoadingObjectException, SignatureException, NoDocumentToSignException {
		
		logger.debug("[XAdESXLSignature.signDetached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, urlDocument, urlTSA, caList }));
		
		//-- Obtener el document de Arangi y llamar a la firma attached
		URLDocument document;
		try {
			document = new URLDocument(urlDocument);
		} catch (InitDocumentException e) {
			logger.info("[XAdESXLSignature.signDetached]::La URL a firmar no existe o es nula:: " + urlDocument);
			throw new NoDocumentToSignException("La URL a firmar no existe o es nula: " + urlDocument);
		}
		
		//-- Obtener la referencia al fichero
		String reference = urlDocument.toString();
		
		return signDetached(manager, alias, document, reference, urlTSA, caList);
	}
	
	/**
	 * Realiza una firma XAdES-XL detached (el fichero no se incluirá en la firma). No completa los campos 
	 * no obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 
	 * 'signatureProductionPlace' y 'signerRole'.
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Documento a firmar. 
	 * @param reference Referencia a la que apuntará la firma
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 */
	public static XAdESXLSignature signDetached (DeviceManager manager, String alias, IDocument document, String reference, URL urlTSA, CAList caList) throws LoadingObjectException, SignatureException {
		
		logger.debug("[XAdESXLSignature.signDetached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, reference, urlTSA, caList }));
		
		return (XAdESXLSignature) signDetached(manager, alias, document, reference, urlTSA, new ArangiCertStatusRecover(caList), EnumFormatoFirma.XAdES_XL, XAdESXLSignature.class);
	}
	
	/**
	 * Realiza una firma XAdES-XL atached (el documento se incluye en la firma). No completa los campos no 
	 * obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 'signatureProductionPlace' 
	 * y 'signerRole'.
	 * 
	 * Si el documento es un XML y los parámetros <code>idToSign</code> y <code>signatureParent</code>
	 * no son nulos la firma y los campos propios de XAdES se añadirán al XML. En caso contrario el fichero 
	 * XAdES resultante seguirá la plantilla de Arangí, por ejemplo:<br>
	 * <code>
	 * 	&lt;arangi-xades&gt;<br>
	 *  &nbsp;&nbsp;&lt;document&gt;...&lt;/document&gt;
	 *  &nbsp;&nbsp;&lt;ds:Signature&gt;...&lt;/ds:Signature&gt;
	 * 	&lt;/arangi-xades&gt;<br>
	 * </code>
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Documento a firmar
	 * @param idToSign Valor del atributo 'ID' que indica lo que se firmará dentro del documento. Si tiene 
	 *  valor nulo el XML de la firma tendrá el formato por defecto de las firmas XAdES de Arangí.
	 * @param signatureParent Nombre del tag que será el padre de los nodos de firma. Si tiene valor nulo
	 * 	la firma colgará del nodo raíz.
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws XMLDocumentException Error montando el fichero XML
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 */
	public static XAdESXLSignature signAttached (DeviceManager manager, String alias, IDocument document, String idToSign,
			String signatureParent, URL urlTSA, CAList caList) throws XMLDocumentException, LoadingObjectException, SignatureException  {
		
		logger.debug("[XAdESXLSignature.signAttached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, idToSign, signatureParent, urlTSA, caList }));
	
		return (XAdESXLSignature) signAttached(manager, alias, document, idToSign, signatureParent, urlTSA, new ArangiCertStatusRecover(caList), EnumFormatoFirma.XAdES_XL, XAdESXLSignature.class);
	}
	
	/**
	 * Realiza una firma XAdES-XL atached (el documento se incluye en la firma). No completa los campos no 
	 * obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 'signatureProductionPlace' 
	 * y 'signerRole'.<br><br>
	 * 
	 * El fichero XAdES seguirá la plantilla de Arangí. Ejemplo:<br>
	 * <code>
	 * 	&lt;arangi-xades&gt;<br>
	 *  &nbsp;&nbsp;&lt;document&gt;...&lt;/document&gt;
	 *  &nbsp;&nbsp;&lt;ds:Signature&gt;...&lt;/ds:Signature&gt;
	 * 	&lt;/arangi-xades&gt;<br>
	 * </code>
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Documento a firmar
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws XMLDocumentException Error montando el fichero XML
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 */
	public static XAdESXLSignature signAttached (DeviceManager manager, String alias, IDocument document, 
			URL urlTSA, CAList caList) throws XMLDocumentException, LoadingObjectException, SignatureException  {
		
		logger.debug("[XAdESXLSignature.signAttached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, urlTSA, caList }));
	
		return (XAdESXLSignature) signAttached(manager, alias, document, null, null, urlTSA, new ArangiCertStatusRecover(caList), EnumFormatoFirma.XAdES_XL, XAdESXLSignature.class);
	}
	
	/**
	 * Realiza una firma XAdES-XL atached (el documento se incluye en la firma). No completa los campos no 
	 * obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 'signatureProductionPlace' 
	 * y 'signerRole'.<br><br>
	 * 
	 * El fichero XAdES seguirá la plantilla de Arangí. Ejemplo:<br>
	 * <code>
	 * 	&lt;arangi-xades&gt;<br>
	 *  &nbsp;&nbsp;&lt;document&gt;...&lt;/document&gt;
	 *  &nbsp;&nbsp;&lt;ds:Signature&gt;...&lt;/ds:Signature&gt;
	 * 	&lt;/arangi-xades&gt;<br>
	 * </code>
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Fichero a firmar
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws XMLDocumentException Error montando el fichero XML
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 */
	public static XAdESXLSignature signAttached (DeviceManager manager, String alias, File document, URL urlTSA, CAList caList) throws LoadingObjectException, SignatureException, NoDocumentToSignException, XMLDocumentException {
		
		logger.debug("[XAdESXLSignature.signAttached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, urlTSA, caList }));
		
		//-- Obtener el document de Arangi y llamar a la firma attached
		FileDocument fileDocument;
		try {
			fileDocument = new FileDocument(document);
		} catch (InitDocumentException e) {
			logger.info("[XAdESXLSignature.signAttached]::El fichero a firmar no existe o es nulo:: " + document);
			throw new NoDocumentToSignException("El fichero a firmar no existe o es nulo: " + document);
		}
		
		return signAttached(manager, alias, fileDocument, urlTSA, caList);
		
	}
	
	/**
	 * Realiza una firma XAdES-XL atached (el documento se incluye en la firma). No completa los campos no 
	 * obligatorios del tag 'SignedSignatureProperties':'signaturePolicyIdentifier', 'signatureProductionPlace' 
	 * y 'signerRole'.<br><br>
	 * 
	 * El fichero XAdES seguirá la plantilla de Arangí. Ejemplo:<br>
	 * <code>
	 * 	&lt;arangi-xades&gt;<br>
	 *  &nbsp;&nbsp;&lt;document&gt;...&lt;/document&gt;
	 *  &nbsp;&nbsp;&lt;ds:Signature&gt;...&lt;/ds:Signature&gt;
	 * 	&lt;/arangi-xades&gt;<br>
	 * </code>
	 * 
	 * @param manager Dispositivo criptográfico que realizará la firma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param document Fichero a firmar (se encuentra en una URL accesible)
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @param caList Lista de CAs para obtener información de validación
	 * @return Firma XADES-XL
	 * @throws XMLDocumentException Error montando el fichero XML
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No se puede realizar la firma
	 */
	public static XAdESXLSignature signAttached (DeviceManager manager, String alias, URL document, URL urlTSA, CAList caList) throws LoadingObjectException, SignatureException, NoDocumentToSignException, XMLDocumentException {
		
		logger.debug("[XAdESTSignature.signAttached]::Entrada::" + Arrays.asList(new Object[] { manager, alias, document, urlTSA, caList }));
		
		//-- Obtener el document de Arangi y llamar a la firma attached
		URLDocument urlDocument;
		try {
			urlDocument = new URLDocument(document);
		} catch (InitDocumentException e) {
			logger.info("[XAdESTSignature.signAttached]::La URL a firmar no existe o es nula:: " + document);
			throw new NoDocumentToSignException("La URL a firmar no existe o es nula: " + document);
		}
		
		return signAttached(manager, alias, urlDocument, urlTSA, caList);
		
	}
	
	/**
	 * La definición de las contrafirmas en XAdES puede observarse en el punto 7.2.4
	 * del estándar de la ETSI.<br><br>
	 * 
	 * Este método realiza una contrafirma para la última firma del XAdES. Es útil 
	 * cuando se sabe que el XAdES contiene sólo una firma.<br><br>
	 * 
	 * Como resultado el XAdES a la que hace referencia este objeto se modificará 
	 * para añadir la contrafirma.
	 * 
	 * @param manager Dispositivo criptográfico que realizará la contrafirma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
	 * @param caList Lista de CAs para obtener información de validación
	 * @throws LoadingObjectException No es posible obtener la clave privada o el
	 * 	certificado del alias
	 * @throws CounterSignatureException Errores durante el proceso de contrafirma
	 */
	public void counterSign (DeviceManager manager, String alias, URL urlTSA, CAList caList) throws LoadingObjectException, CounterSignatureException {
		counterSign(manager, alias, null, urlTSA, caList);
	}
	
	/**
	 * La definición de las contrafirmas en XAdES puede observarse en el punto 7.2.4
	 * del estándar de la ETSI.<br><br>
	 * 
	 * Este método realiza una contrafirma para la firma cuyo certificado se pasa
	 * en el parámetro 'signatureToCounterSignCertificate'. Es útil cuando se quiere
	 * contrafirmar un XAdES que contiene varias firmas. Para saber qué firma se
	 * desea contrafirmar se puede llamar primero a 
	 * {@link #getCertificates() getCertificates} para ver los certificados de cada
	 * una de las firmas que contiene el XAdES.<br><br>
	 * 
	 * Como resultado el XAdES a la que hace referencia este objeto se modificará 
	 * para añadir la contrafirma.
	 * 
	 * @param manager Dispositivo criptográfico que realizará la contrafirma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param signatureToCounterSignCertificate Certificado de la firma que se 
	 * 	contrafirmará
	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
	 * @param caList Lista de CAs para obtener información de validación
	 * @throws LoadingObjectException No es posible obtener la clave privada o el
	 * 	certificado del alias
	 * @throws CounterSignatureException Errores durante el proceso de contrafirma
	 */
	public void counterSign (DeviceManager manager, String alias, Certificate signatureToCounterSignCertificate, URL urlTSA, CAList caList) throws LoadingObjectException, CounterSignatureException {
		
		logger.debug("[XAdESXLSignature.counterSign]::Entrada::" + Arrays.asList(new Object[] { manager, alias, signatureToCounterSignCertificate, urlTSA, caList }));
		
		//-- El documento a firmar es la firma en la que nos encontramos
        DataToSign dataToSign = new DataToSign();
        dataToSign.setXadesFormat(getXAdESSignatureFormat());
        dataToSign.setEsquema(XAdESSchemas.XAdES_132);
        dataToSign.setXMLEncoding("UTF-8");
        dataToSign.setEnveloped(true);
        dataToSign.setDocument(this.xadesDocument);
       	dataToSign.setCertStatusManager (new ArangiCertStatusRecover(caList));
        
		Document doc;
		try {
			doc = ContraFirmaXML.counterSign(manager.getCertificate(alias), dataToSign, signatureToCounterSignCertificate!=null?signatureToCounterSignCertificate.toX509Certificate():null, 
					manager.getPrivateKey(alias), urlTSA, XAdESSchemas.XAdES_132.getSchemaUri());
		} catch (LoadingObjectException e) {
			logger.info("[XAdESXLSignature.counterSign]::No es posible obtener la clave privada del alias '" + alias + "'", e);
			throw e;
		} catch (SearchingException e) {
			logger.info("[XAdESXLSignature.counterSign]::No es posible obtener el certificado del alias '" + alias + "'", e);
			throw new LoadingObjectException ("No es posible obtener el certificado del alias '" + alias + "'", e);
		} catch (CounterSignatureException e) {
			logger.info("[XAdESXLSignature.counterSign]::No es posible realizar la contrafirma", e);
			throw e;
		}
		
		logger.debug("[XAdESXLSignature.counterSign]::Se ha obtenido la contrafirma");
		this.xadesDocument = doc;
	}
	
	/**
	 * Añade una Cofirma a la firma XAdES-XL. Realizará una firma de las mismas características que 
	 * la primera que encuentre (attached o dettached).<br><br>
	 * 
	 * Si la firma es dettached i la referencia al documento que hay en la firma no
	 * es una URL será necesario usar el método {@link #coSign(DeviceManager, String, IDocument)}
	 * al que le proporcionaremos este documento.  
	 * 
	 * @param manager Dispositivo criptográfico que realizará la cofirma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
	 * @param caList Lista de CAs para obtener información de validación
	 * @throws SignatureNotFoundException No existe ninguna firma que cofirmar
	 * @throws NoDocumentToSignException El fichero a firmar no existe o es nulo
	 * @throws HashingException Error realizando el hash del documento
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No ha sido posible parsear la firma XAdES o no se puede realizar la cofirma
	 * @throws NoCoincidentDocumentException El documento que se quiere firmar no se corresponde con el de
	 * 	la firma XAdES  
	 */
	public void coSign (DeviceManager manager, String alias, URL urlTSA, CAList caList) throws SignatureNotFoundException, 
		NoDocumentToSignException, HashingException, LoadingObjectException, SignatureException, NoCoincidentDocumentException {
		
		logger.debug("[XAdESXLSignature.coSign]::Entrada::" + Arrays.asList(new Object[] { manager, alias }));
		coSign (manager, alias, null, urlTSA, caList);
	}
	
	
	/**
	 * Añade una Cofirma a la firma XAdES-XL. Realizará una firma de las mismas características que 
	 * la primera que encuentre (attached o dettached).<br><br>
	 * 
	 * Este método es útil si la firma es dettached i la referencia al documento que hay en la firma no
	 * es una URL.  
	 * 
	 * @param manager Dispositivo criptográfico que realizará la cofirma
	 * @param alias Alias donde se encuentra la clave privada dentro del dispositivo
	 * @param signedDoc contenido a firmar. El mismo utilizado en la generación de las otras firmas
	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
	 * @param caList Lista de CAs para obtener información de validación
	 * @throws SignatureNotFoundException No existe ninguna firma que cofirmar
	 * @throws NoDocumentToSignException El fichero a firmar no existe o es nulo
	 * @throws HashingException Error realizando el hash del documento
	 * @throws LoadingObjectException No ha sido posible cargar la clave privada o el certificado usados
	 *  para realizar la firma
	 * @throws SignatureException No ha sido posible parsear la firma XAdES o no se puede realizar la cofirma
	 * @throws NoCoincidentDocumentException El documento que se quiere firmar no se corresponde con el de
	 * 	la firma XAdES  
	 */
	public void coSign (DeviceManager manager, String alias, IDocument signedDoc, URL urlTSA, CAList caList) throws SignatureNotFoundException, 
		NoDocumentToSignException, HashingException, LoadingObjectException, SignatureException, NoCoincidentDocumentException {
		
		coSign(manager, alias, signedDoc, urlTSA, new ArangiCertStatusRecover(caList), EnumFormatoFirma.XAdES_XL, XAdESXLSignature.class);
		
	}
	
	/**
	 * Devuelve una cadena de texto con el tipo de la firma
	 * 
	 * @return Cadena de texto con el tipo de la firma
	 */
	public String getSignatureType () {
		return "XAdES-X-L";
	}
	
	/**
	 * Añade lo que falta para completar el XAdES-T a XAdES-X-L. La firma tiene que ser attached. Si no es así es necesario
	 * usar {@link #completeToXAdESXL (XAdESTSignature, IDocument, CAList, URL)}.
	 * 
	 * @param xadesT Firma XAdES-T
	 * @param CAList lista de certificados de CA
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @return Firma XAdES-X-L
	 * @throws NormalizeCertificateException Alguno de los certificados no puede ser 
	 * 	normalizado al formato reconocido por el proveedor criptográfico de Arangí o su 
	 * 	firma no es correcta o no puede ser analizada
	 * @throws SignatureException Error tratando el objeto firma o la firma XAdES-T no es
	 * 	válida
	 * @throws RetrieveOCSPException Error obteniendo las respuestas OCSP
	 * @throws XMLDocumentException Error completando el XML del XAdES-T a XAdES-X-L
	 */
	public static XAdESXLSignature completeToXAdESXL (XAdESTSignature xadesT, CAList caList, URL urlTSA) throws SignatureException, NormalizeCertificateException, RetrieveOCSPException, XMLDocumentException {
		return completeToXAdESXL (xadesT, null, caList, urlTSA);
	}
	
	/**
	 * Añade lo que falta para completar el XAdES-T a XAdES-X-L
	 * 
	 * @param xadesT Firma XAdES-T
	 * @param document documento firmado en el XAdES-T. Útil en el caso que el XAdES sea detached.
	 * @param CAList lista de certificados de CA
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @return Firma XAdES-X-L
	 * @throws NormalizeCertificateException Alguno de los certificados no puede ser 
	 * 	normalizado al formato reconocido por el proveedor criptográfico de Arangí o su 
	 * 	firma no es correcta o no puede ser analizada
	 * @throws SignatureException Error tratando el objeto firma o la firma XAdES-T no es
	 * 	válida
	 * @throws RetrieveOCSPException Error obteniendo las respuestas OCSP
	 * @throws XMLDocumentException Error completando el XML del XAdES-T a XAdES-X-L
	 */
	public static XAdESXLSignature completeToXAdESXL (XAdESTSignature xadesT, IDocument document, CAList caList, URL urlTSA) throws SignatureException, NormalizeCertificateException, RetrieveOCSPException, XMLDocumentException {
		logger.debug("[XAdESXLSignature.completeToXAdESXL]::Entrada::" + Arrays.asList(new Object[] { xadesT }));
		
		Document doc = xadesT.getDOM();
		
		//-- Comprobar que el XAdES-T es correcto
		logger.debug("[XAdESXLSignature.completeToXAdESXL]::Verificando que el XAdES-T es correcto");
		ValidationResult[] resultadoValidacion = null;
		if (document == null) {
			resultadoValidacion = xadesT.isValid(caList);			
		} else {
			resultadoValidacion = xadesT.isValid(document, caList);
		}
		for (int i = 0; i < resultadoValidacion.length; i++) {
			if (!resultadoValidacion[i].isValid()) {
				logger.info("[XAdESXLSignature.completeToXAdESXL]::El XAdES-T no es válido: " + resultadoValidacion[i].getResultText());
				throw new SignatureException("El XAdES-T no es válido: " + resultadoValidacion[i].getResultText());
			}
		}
		
		//-- Obtenemos el id del tag de firma (existirá o no se hubiese pasado la anterior validación)
		NodeList listaFirmas = doc.getElementsByTagNameNS(ConstantesXADES.SCHEMA_DSIG, ConstantesXADES.LIBRERIAXADES_SIGNATURE);
		Node idAttribute = listaFirmas.item(0).getAttributes().getNamedItem(ConstantesXADES.ID);
		
		// Se obtiene el certificado firmante
    	Element certificateNode = (Element) doc.getElementsByTagNameNS(ConstantesXADES.SCHEMA_DSIG, "X509Certificate").item(0);
    	byte[] certificateContent = Util.decodeBase64(certificateNode.getFirstChild().getNodeValue());
    	X509Certificate certificate = (X509Certificate) Util.getCertificate(certificateContent);

		//-- Completar a XAdES-C
		ArrayList<RespYCerts> respuestas = new ArrayList<RespYCerts>();
		try {
			logger.debug("[XAdESXLSignature.completeToXAdESXL]::Obteniendo respuestas OCSP");
			respuestas = convertICertStatus2RespYCerts(new ArangiCertStatusRecover(caList).getCertChainStatus(certificate));
		} catch (CertStatusException e) {
			logger.info("[XAdESXLSignature.completeToXAdESXL]::Error obteniendo respuestas OCSP", e);
			throw new RetrieveOCSPException("Error obteniendo respuestas OCSP", e);
		}
		
		try {
			doc = addXadesC((Element) listaFirmas.item(0), respuestas, XAdESSchemas.XAdES_132, UtilidadFirmaElectronica.DIGEST_ALG_SHA1);
			logger.debug("[XAdESXLSignature.completeToXAdESXL]::Completado a XAdES-C");
		} catch (Exception e) {
			throw new XMLDocumentException(e);
		}
		
		//-- Completar a XAdES-X
		// A partir del nodo raíz de la firma se obtiene el nodo UnsignedSignatureProperties
		Element unsignedSignaturePropertiesElement = null;
		NodeList unsignedSignaturePropertiesNodes = 
			doc.getElementsByTagNameNS(xadesSchema, ConstantesXADES.UNSIGNED_SIGNATURE_PROPERTIES);
		
		if (unsignedSignaturePropertiesNodes.getLength() != 1) {
			// El nodo UnsignedSignatureProperties no existe o no es único 
			logger.info("[XAdESXLSignature.completeToXAdESXL]::El nodo UnsignedSignatureProperties no existe o no es único");
			// El sistema no soporta nodos UnsignedSignatureProperties múltiples
			throw new XMLDocumentException("El nodo UnsignedSignatureProperties no existe o no es único");
		} else {
			unsignedSignaturePropertiesElement = (Element) unsignedSignaturePropertiesNodes.item(0);
		}
		
		// Se añaden los elementos propios de la firma XADES-X
		try {
			doc = addXadesX(unsignedSignaturePropertiesElement, urlTSA, ConstantesTSA.SHA1);
			logger.debug("[XAdESXLSignature.completeToXAdESXL]::Completado a XAdES-X");
		} catch (Exception e) {
			throw new XMLDocumentException(e);
		}

		//-- Completar a XAdES-X-L
		try {
			doc = addXadesXL((Element) listaFirmas.item(0), respuestas, XAdESSchemas.getXAdESSchema(xadesSchema));
			logger.debug("[XAdESXLSignature.completeToXAdESXL]::Completado a XAdES-X-L");
		} catch (Exception e) {
			throw new XMLDocumentException(e);
		}
		
		return new XAdESXLSignature(doc);
	}
	
	/**
	 * Añade lo que falta para completar el XAdES-BES a XAdES-X-L. La firma tiene que ser attached. Si no es así es necesario
	 * usar {@link #completeToXAdESXL (XAdESBESSignature, IDocument, CAList, URL)}.
	 * 
	 * @param xadesBES Firma XAdES-BES
	 * @param CAList lista de certificados de CA
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @return Firma XAdES-X-L
	 * @throws ResponseTimeStampException No se ha podido obtener correctamente el sello de tiempos
	 * @throws MalformedTimeStampException El sello de tiempos obtenido no está bien formado
	 * @throws NormalizeCertificateException Alguno de los certificados no puede ser 
	 * 	normalizado al formato reconocido por el proveedor criptográfico de Arangí o su 
	 * 	firma no es correcta o no puede ser analizada
	 * @throws SignatureException Error tratando el objeto firma o la firma XAdES-T no es
	 * 	válida
	 * @throws HashingException Error obteniendo el hash del documento
	 * @throws RetrieveOCSPException Error obteniendo las respuestas OCSP
	 * @throws XMLDocumentException Error completando el XML del XAdES-T a XAdES-X-L
	 */
	public static XAdESXLSignature completeToXAdESXL (XAdESBESSignature xadesBES, CAList caList, URL urlTSA) throws SignatureException, MalformedTimeStampException, ResponseTimeStampException, HashingException, XMLDocumentException, NormalizeCertificateException, RetrieveOCSPException {
		return completeToXAdESXL (xadesBES, null, caList, urlTSA);
	}

	
	/**
	 * Añade lo que falta para completar el XAdES-BES a XAdES-X-L
	 * 
	 * @param xadesBES Firma XAdES-BES
	 * @param document documento firmado en el XAdES-T. Útil en el caso que el XAdES sea detached.
	 * @param CAList lista de certificados de CA
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @return Firma XAdES-X-L
	 * @throws ResponseTimeStampException No se ha podido obtener correctamente el sello de tiempos
	 * @throws MalformedTimeStampException El sello de tiempos obtenido no está bien formado
	 * @throws NormalizeCertificateException Alguno de los certificados no puede ser 
	 * 	normalizado al formato reconocido por el proveedor criptográfico de Arangí o su 
	 * 	firma no es correcta o no puede ser analizada
	 * @throws SignatureException Error tratando el objeto firma o la firma XAdES-T no es
	 * 	válida
	 * @throws HashingException Error obteniendo el hash del documento
	 * @throws RetrieveOCSPException Error obteniendo las respuestas OCSP
	 * @throws XMLDocumentException Error completando el XML del XAdES-T a XAdES-X-L
	 */
	public static XAdESXLSignature completeToXAdESXL (XAdESBESSignature xadesBES, IDocument document, CAList caList, URL urlTSA) throws SignatureException, MalformedTimeStampException, ResponseTimeStampException, HashingException, XMLDocumentException, NormalizeCertificateException, RetrieveOCSPException {
		logger.debug("[XAdESXLSignature.completeToXAdESXL]::Entrada::" + Arrays.asList(new Object[] { xadesBES }));
		
		//-- Completar a XAdES-T
		XAdESTSignature xadesT = XAdESTSignature.completeToXAdEST(xadesBES, document, caList, urlTSA);
		
		//-- Completar a XAdES-XL		
		return completeToXAdESXL(xadesT, document, caList, urlTSA);
	}
	
	/**
	 * Añade un sello de tiempos de archivado a todas las firmas incluidas en este
	 * fichero XAdES-X-L (convirtiéndolo, si no lo era ya, en un XAdES-A).
	 * 
	 * @param urlTSA URL del servidor de sello de tiempos
	 * @throws SignatureException Error tratando con las firmas contenidas en el XAdES-X-L
	 * @throws TimeStampException Error obteniendo el sello de tiempos
	 * @throws XMLDocumentException Error modificando el XML para añadirle los nuevos sellos
	 * 	de tiempos
	 */
	public void addArchiveTimeStamp (URL urlTSA) throws SignatureException, TimeStampException, XMLDocumentException {
		logger.debug("[XAdESXLSignature.addArchiveTimeStamp]::Entrada");
		
		//-- Por si acaso inicializar el xmlsec
		org.apache.xml.security.Init.init();
		
		try {
			TSCliente tsCliente = new TSCliente(urlTSA.toString(), ConstantesTSA.SHA1);
			
			//-- Obtener los nodos de firma
			String uriXmlNS = "http://www.w3.org/2000/09/xmldsig#";
			NodeList signatureList = xadesDocument.getElementsByTagNameNS(uriXmlNS, "Signature");
			
			//-- Recorrer los nodos de firma
			for(int i=0;i<signatureList.getLength();i++) {
				XMLSignature firma = new XMLSignature((Element)signatureList.item(i), "./");
				logger.debug("[XAdESXLSignature.addArchiveTimeStamp]::Resellando la firma: " + firma.getId());
				
				// Para los esquemas 1.1.1 y 1.2.2 se añaden includes con los nodos tomados
				ArrayList<String> inc = null;
				if (ConstantesXADES.SCHEMA_XADES_111.equals(xadesSchema) || ConstantesXADES.SCHEMA_XADES_122.equals(xadesSchema)) {
					inc = UtilidadXadesA.obtenerListadoIdsElementosXadesA(xadesSchema, firma, null);
				}
				
				// Se obtiene la cadena de bytes de entrada del sello XAdES-A
				byte[] input = UtilidadXadesA.obtenerListadoXadesA(xadesSchema, firma, null);
				addXadesA(firma.getElement(), tsCliente.generarSelloTiempo(input), inc) ;
				logger.debug("[XAdESXLSignature.addArchiveTimeStamp]::Resellada la firma: " + firma.getId());
			}
		} catch (XMLSignatureException e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::Uno de los elementos 'Signature' está mal formado", e);
			throw new SignatureException("Uno de los elementos 'Signature' está mal formado", e);
		} catch (XMLSecurityException e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::Error de seguridad accediendo a uno de los elementos 'Signature'", e);
			throw new SignatureException("Error de seguridad accediendo a uno de los elementos 'Signature'", e);
		} catch (BadFormedSignatureException e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::Uno de los elementos 'Signature' está mal formado", e);
			throw new SignatureException("Uno de los elementos 'Signature' está mal formado", e);
		} catch (FirmaXMLError e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::Uno de los elementos 'Signature' está mal formado", e);
			throw new SignatureException("Uno de los elementos 'Signature' está mal formado", e);
		} catch (IOException e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::No se puede obtener o leer el documento que originó la firma, o no es posible escribir en memoria", e);
			throw new SignatureException("No se puede obtener o leer el documento que originó la firma, o no es posible escribir en memoria", e);
		} catch (TSClienteError e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::No se puede obtener el sello de tiempos", e);
			throw new TimeStampException("No se puede obtener el sello de tiempos", e);
		} catch (Exception e) {
			logger.info("[XAdESXLSignature.addArchiveTimeStamp]::No se puede añadir el sello de tiempos de archivo a una de las firmas", e);
			throw new XMLDocumentException("No se puede añadir el sello de tiempos de archivo a una de las firmas", e);
		} 
		
	}
	
	/**
	 * Devuelve la fecha en la que caducará el certificado de la TSA incluido en el
	 * sello de tiempos. En caso de que los sellos de tiempos estén firmados por 
	 * varias TSA se devolverá la fecha de caducidad del certificado de TSA más cercana 
	 * a caducar.<br><br>
	 * 
	 * En el caso de que ya se haya resellado (las firmas sean XAdES-A) se devolverá
	 * la fecha de caducidad del certificado de TSA del último resellado.
	 * 
	 * @return Fecha de caducidad del certificado de TSA 
	 * @throws MalformedTimeStampException Alguno de los sellos de tiempos no está bien formado
	 */
	public Date getTimeStampCertificateExpiration () throws MalformedTimeStampException {
		logger.debug("[XAdESXLSignature.getTimeStampCertificateExpiration]::Entrada");
		
		//-- Buscar los nodos ArchiveTimeStamp (si existen)
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		NodeList nodeList = null;
		try {
			XPathExpression expr = xpath.compile("//*[local-name()='" + ConstantesXADES.ARCHIVE_TIME_STAMP + "']/*[local-name()='EncapsulatedTimeStamp']");
			nodeList = (NodeList) expr.evaluate (xadesDocument, XPathConstants.NODESET);
		} catch (Exception e) {
			logger.info ("[XAdESXLSignature.getTimeStampCertificateExpiration]::Error inesperado", e);
			return null;
		}
		
		Date fecha = null;
		for (int i = 0; i < nodeList.getLength(); i++) {
			//-- Obtener el sello de tiempos
			TimeStamp timeStamp = new TimeStamp(new ByteArrayInputStream (Util.decodeBase64(nodeList.item(i).getTextContent())));
			
			//-- Obtener fecha 
			Date timeStampDate = timeStamp.getSignatureCertificate().getValidityPeriodEnd();
			if (fecha == null) {
				fecha = timeStampDate;
			}
			if (fecha.before(timeStampDate)) {
				fecha = timeStampDate;
			}
		}
		
		if (fecha != null) {
			logger.debug("[XAdESXLSignature.getTimeStampCertificateExpiration]::Fecha obtenida del ultimo resellado: " + fecha);
			return fecha;
		}

		//-- Obtener todos los nodos sello de tiempo
		try {
			XPathExpression expr = xpath.compile("//*[local-name()='EncapsulatedTimeStamp']");
			nodeList = (NodeList) expr.evaluate (xadesDocument, XPathConstants.NODESET);
		} catch (Exception e) {
			logger.info ("[XAdESXLSignature.getTimeStampCertificateExpiration]::Error inesperado", e);
			return null;
		}
		
		fecha = null;
		for (int i = 0; i < nodeList.getLength(); i++) {
			//-- Obtener el sello de tiempos
			TimeStamp timeStamp = new TimeStamp(new ByteArrayInputStream (Util.decodeBase64(nodeList.item(i).getTextContent())));
			
			//-- Obtener fecha 
			Date timeStampDate = timeStamp.getSignatureCertificate().getValidityPeriodEnd();
			if (fecha == null) {
				fecha = timeStampDate;
			}
			if (fecha.after(timeStampDate)) {
				fecha = timeStampDate;
			}
		}
		
		logger.debug("[XAdESXLSignature.getTimeStampCertificateExpiration]::Fecha obtenida del certificado TSA más próximo a caducar: " + fecha);
		return fecha;
		

	}

	//-- Implementación de XAdESSignature
	
	@Override
	protected int tratarResultadoValidacion(ResultadoValidacion resultadoValidacion) {
		if (resultadoValidacion.isValidate() && resultadoValidacion.getEnumNivel() == EnumFormatoFirma.XAdES_XL) {
			//válido
			logger.debug("[XAdESXLSignature.isValidSignatureOnly]::La firma ha pasado la validación");
			return ValidationResult.RESULT_VALID;
		} else if (resultadoValidacion.getNivelValido() == null || resultadoValidacion.getNivelValido().equals("")) {
			if (resultadoValidacion.getLog().toLowerCase().indexOf("firma inválida") > -1) {
				//certificado válido, el problema será con la firma
				logger.debug("[XAdESXLSignature.isValidSignatureOnly]::La firma no es válida");
				return ValidationResult.RESULT_SIGNATURE_NOT_MATCH_DATA;
			} else {
				//certificado caducado
				logger.debug("[XAdESXLSignature.isValidSignatureOnly]::El certificado está caducado");
				return ValidationResult.RESULT_CERTIFICATE_NOT_ACTIVE;
			}
		} else if (resultadoValidacion.getEnumNivel() == EnumFormatoFirma.XAdES_BES) {
			//El BES es válido, o sea que es un problema con el sello de tiempos
			logger.debug("[XAdESXLSignature.isValidSignatureOnly]::El sello de tiempos no es válido");
			return ValidationResult.RESULT_INVALID_TIMESTAMP;
		} else if (resultadoValidacion.getResultado() == ResultadoEnum.INVALID) {
			//revocado
			logger.debug("[XAdESXLSignature.isValidSignatureOnly]::El certificado de la firma está revocado");
			return ValidationResult.RESULT_CERTIFICATE_REVOKED;
		} else if (resultadoValidacion.getEnumNivel() == EnumFormatoFirma.XAdES_T) {
			//no hay información de revocación (respuestas OCSP)
			logger.debug("[XAdESXLSignature.isValidSignatureOnly]::El XAdES es un XAdES-T");
			return ValidationResult.RESULT_CERTIFICATE_CANNOT_BE_VALIDATED;
		} else  {
			//desconocido
			logger.debug("[XAdESXLSignature.isValidSignatureOnly]::La firma no ha pasado la validación");
			return ValidationResult.RESULT_CERTIFICATE_UNKNOWN;
		}
	}
	
	/**
	 * Formato de firma: XAdES-X-L
	 */
	protected EnumFormatoFirma getXAdESSignatureFormat () {
		return EnumFormatoFirma.XAdES_XL;
	}
	

	//-- Métodos privados

	/*
	 * Añade el sello de archivo a una firma
	 */
    private void addXadesA (Element firma, byte[] selloTiempo, ArrayList<String> inc) throws Exception {
    	
    	Document doc = firma.getOwnerDocument();
    	
    	ArrayList<Element> UnsignedSignaturePropertiesNodes = UtilidadTratarNodo.obtenerNodos(firma, 4, 
				new NombreNodo(xadesSchema, ConstantesXADES.UNSIGNED_SIGNATURE_PROPERTIES));
    	Element UnsignedSignaturePropertiesNode = null;
    	if(UnsignedSignaturePropertiesNodes.size() == 1)
    		UnsignedSignaturePropertiesNode = (Element)UnsignedSignaturePropertiesNodes.get(0);
    	else
    		// No se encuentra el nodo UnsignedSignatureProperties
    		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_19)) ;

    	Element archiveTimeStamp =
    		doc.createElementNS(xadesSchema, xadesNS + ConstantesXADES.DOS_PUNTOS + ConstantesXADES.ARCHIVE_TIME_STAMP);
    	
     	// Creamos los atributos de ArchiveTimeStamp (Id)
     	Attr archiveTimeStampId = doc.createAttributeNS(null, ConstantesXADES.ID);
     	archiveTimeStampId.setValue(UtilidadTratarNodo.newID(doc, 
     			ConstantesXADES.ARCHIVE_TIME_STAMP + ConstantesXADES.GUION));
     	NamedNodeMap archiveTimeStampAttributesElement =
     		archiveTimeStamp.getAttributes();
     	archiveTimeStampAttributesElement.setNamedItem(archiveTimeStampId);
     	
     	// Se agrega el nodo EncapsulatedTimeStamp, con Id y Encoding como atributos
     	Element encapsulatedTimeStamp =
     		doc.createElementNS(xadesSchema, xadesNS + ConstantesXADES.DOS_PUNTOS + 
     				ConstantesXADES.ENCAPSULATED_TIME_STAMP);
     	
     	// Se escribe una Id Ãºnica
     	Attr informacionElementoSigTimeStamp = doc.createAttributeNS(null, ConstantesXADES.ID);
     	String idSelloTiempo = UtilidadTratarNodo.newID(doc, ConstantesXADES.SELLO_TIEMPO_TOKEN);
     	informacionElementoSigTimeStamp.setValue(idSelloTiempo);
     	encapsulatedTimeStamp.getAttributes().setNamedItem(informacionElementoSigTimeStamp);
     	
     	// Se agrega el CanonicalizationMethod
     	Element canonicalizationElemento = doc.createElementNS(ConstantesXADES.SCHEMA_DSIG, xmldsigNS + ConstantesXADES.DOS_PUNTOS + ConstantesXADES.CANONICALIZATION_METHOD);		
 		Attr canonicalizationAttribute = doc.createAttributeNS(null, ConstantesXADES.ALGORITHM);
 		canonicalizationAttribute.setValue(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);
 		canonicalizationElemento.getAttributes().setNamedItem(canonicalizationAttribute);
 
 		archiveTimeStamp.appendChild(canonicalizationElemento);
         
         encapsulatedTimeStamp.appendChild(doc.createTextNode(new String(Base64Coder.encode(selloTiempo))));       
         
         // Se agregan, si existen, los nodos include
         if (inc != null) {
         	Element includeNode = null;
         	for (int i = 0; i < inc.size(); ++i) {
         		includeNode = doc.createElementNS(xadesSchema, xadesNS + ConstantesXADES.DOS_PUNTOS + 
         				ConstantesXADES.INCLUDE);      	
         		includeNode.setAttributeNS(null, ConstantesXADES.URI_MAYUS, inc.get(i));
         		archiveTimeStamp.appendChild(includeNode);
         	}
         }
         
         archiveTimeStamp.appendChild(encapsulatedTimeStamp);
         
         // Se agrega el sello creado a las propiedades no firmadas
         UnsignedSignaturePropertiesNode.appendChild(archiveTimeStamp);
         
     }
 
 }